package glemipsc;

import glemipsc.implementations.Instruction;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

public class Linker {
	private HashMap<String, Function> functions = new HashMap<String, Function>();
	private HashMap<String, SymbolElement> variables = new HashMap<String, SymbolElement>();
	private TreeSet<Function> orderedFunctions = new TreeSet<Function>();
	private TreeSet<SymbolElement> orderedVariables = new TreeSet<SymbolElement>();
	
	public Linker (List<ObjectFile> objFiles) {
		Address nextAddress = new AbsoluteAddress(0);
		Iterator<ObjectFile> iter;
		
		// Addressing static variables
		iter = objFiles.iterator();
		while (iter.hasNext()) {
			ObjectFile obj = iter.next();
			List<SymbolElement> vars = obj.getStaticVariables();
			Iterator<SymbolElement> viter = vars.iterator();
			while (viter.hasNext()) {
				SymbolElement el = viter.next();
				el.setStoreAddress(nextAddress);
				if (viter.hasNext())
					//nextAddress = new AbsoluteAddress(nextAddress.getValue() + el.getStoredSize());
					nextAddress = new AbsoluteAddress(nextAddress.getValue() + el.getType().sizeOf() / 4);
				this.variables.put(el.getName(), el);
			}
		}
		// Ordering variables
		Iterator<String> viter = this.variables.keySet().iterator();
		while (viter.hasNext()) {
			orderedVariables.add(variables.get(viter.next()));
		}
		// Linking extern variables
		iter = objFiles.iterator();
		while (iter.hasNext()) {
			iter.next().linkVariables(this);
		}
		// Retrieving all the coded functions
		iter = objFiles.iterator();
		while (iter.hasNext()) {
			ObjectFile obj = iter.next();
			List<Function> functions = obj.getCodedFunctions();
			Iterator<Function> fiter = functions.iterator();
			while (fiter.hasNext()) {
				Function f = fiter.next();
				f.computeOpCode(this);
				this.functions.put(f.getName(), f);
			}
		}
		// Adding StartFunction to functions
		StartFunction start = new StartFunction();
		start.setLinker(this);
		start.initializeVariables(variables);
		functions.put("_start", start);
		start.computeOpCode(this);
		nextAddress = new AbsoluteAddress(0);
		start.setStoreAddress(nextAddress);
		// Addressing functions
		nextAddress = new AbsoluteAddress(start.getStoredSize()/4);
		Iterator<String> fiter = this.functions.keySet().iterator();
		while (fiter.hasNext()) {
			Function f = functions.get(fiter.next());
			if (f.getStoreAddress().getValue() != 0 || f.getName().equals("_start"))
				continue;
			f.setStoreAddress(nextAddress);
			if (fiter.hasNext())
				//nextAddress = new AbsoluteAddress(nextAddress.getValue() + f.getStoredSize());
				nextAddress = new AbsoluteAddress(nextAddress.getValue() + f.getStoredSize()/4);
		}
		// Ordering functions
		fiter = this.functions.keySet().iterator();
		while (fiter.hasNext()) {
			orderedFunctions.add(functions.get(fiter.next()));
		}
		// Linking functions'prototypes
		iter = objFiles.iterator();
		while (iter.hasNext()) {
			iter.next().linkFunctions(this);
		}
	}
	
	public Function getFunction(String name) {
		Function fcnt = functions.get(name);
		if (fcnt == null) {
			GLEMipsC.compiler.displayError(ErrorLevel.ERROR, "Function " + name + " not defined");
			return null;
		}
		return fcnt;
	}

	public SymbolElement getGlobalVariable(String name) {
		return variables.get(name);
	}
	
	public void buildExecutableFile(OutputStream out) {
		if (GLEMipsC.compiler.getEnvironment().asmFile) {
			String signature = "";
			Iterator<SymbolElement> symbolsIter = this.variables.values().iterator();
			while (symbolsIter.hasNext()) {
				SymbolElement symbol = symbolsIter.next();
				signature += "// ";
				signature += symbol.getType().toString() + " " + symbol.getName() + " at " + symbol.getStoreAddress().toString() + "\n";
			}
			try {
				out.write(signature.getBytes());
			}
			catch (IOException e) {
				e.printStackTrace();
			}
		}
		Iterator<Function> iter = orderedFunctions.iterator();
		while (iter.hasNext()) {
			iter.next().write(out);
		}
	}
	
	public List<glemipsc.implementations.Instruction> getBuiltInstructions() {
		ArrayList<Instruction> instructions = new ArrayList<Instruction>();
		Iterator<Function> iter = orderedFunctions.iterator();
		while (iter.hasNext()) {
			instructions.addAll(iter.next().getCompiledInstructions());
		}
		return instructions;
	}
	
	public Function getFunctionByAddress(int addr) {
		Iterator<Function> iter = orderedFunctions.iterator();
		while (iter.hasNext()) {
			Function fcnt = iter.next();
			if (fcnt.getStoreAddress().getValue() <= addr && fcnt.getStoreAddress().getValue() + fcnt.getCompiledInstructions().size() > addr) {
				return fcnt;
			}
		}
		return null;
	}
	
	public int getGlobalDataLength() {
		SymbolElement last = null;
		Iterator<SymbolElement> iter = orderedVariables.iterator();
		while (iter.hasNext()) {
			last = iter.next();
		}
		return last == null ? 0 : (last.getStoreAddress().getValue() + last.getStoredSize());
	}
}
